// Double Metaphone Implementation
//

#include "doublemetaphone.h"

// Convert a word to uppercase
void dmetaphone::UpperCaseWord (BYTE *word, ULONG length) {
	BYTE c;
	for (ULONG i = 0; i < length; i++) {
		c = *(word + i);
		if (c >= 'a' && c <= 'z')
			c = c - 'a' + 'A';
		*(word + i) = c;
	}
}

// If the word contains any of the following:  W, K, CZ or WITZ we consider it
// Slavo-Germanic

bool dmetaphoneword::SlavoGermanic()
{       
	bool flag = FALSE;
	for (ULONG i = 0; i < this->length; i++) {
		if (*(this->word + i) == 'W' || *(this->word + i) == 'K') {
			flag = TRUE;
		} else if (i < this->length && *(this->word + i) == 'C' && *(this->word + i + 1) == 'Z') {
			flag = TRUE;
		} else if (i < this->length - 3 && *(this->word + i) == 'W' && *(this->word + i + 1) == 'I'
		  && *(this->word + i + 2) == 'T' && *(this->word + i + 3) == 'Z') {
			flag = TRUE;
		}
	}
	return flag;
}

// Checks the prefix/first characters of the string
void dmetaphone::CheckStart (dmetaphoneword &inword, dmetaphoneword &outword1, dmetaphoneword &outword2)
{
	// If the first two letters are:

	// "AE-" ==> skip the "A"; technically we ignore one vowel since both encode to "A"
	if (inword.length > 1 && *(inword.word) == 'A') {
		if (*(inword.word + 1) == 'E') {
			outword1.AddChar('A');
			outword2.AddChar('A');
			inword.location += 2;
		}
		// "GN-", "KN-", "PN-" ==> "N"
	} else if (inword.length > 1 && (*(inword.word) == 'G' || *(inword.word) == 'K' || *(inword.word) == 'P'))  {
		if (*(inword.word + 1) == 'N') {
			outword1.AddChar('N');
			outword2.AddChar('N');
			inword.location += 2;
		}
		// "WR-" ==> "R" and "WH-" ==> "W"
	} else if (inword.length > 1 && *(inword.word) == 'W') {
		if (*(inword.word + 1) == 'R') {
			outword1.AddChar('R');
			outword2.AddChar('R');
			inword.location += 2;
		} else if (*(inword.word + 1) == 'H') {
			outword1.AddChar('W');
			outword2.AddChar('W');
			inword.location += 2;
		}
		// "PS-" ==> "S"
	} else if (inword.length > 1 && *(inword.word) == 'P') {
		if (*(inword.word + 1) == 'S') {
			outword1.AddChar('S');
			outword2.AddChar('S');
			inword.location += 2;
		}
		// "X-" ==> "S"
	} else if (inword.length > 0 && *(inword.word) == 'X') {
		outword1.AddChar('S');
		outword2.AddChar('S');
		inword.location++;
	} else if (inword.length > 0 && IsVowel(*(inword.word))) {
		// "[vowel]GN-" ==> "AKN", "AN"
		if(inword.length > 2 && !inword.IsSlavoGermanic && *(inword.word + 1) == 'G'
			&& *(inword.word + 2) == 'N') {
			outword1.AddChar('A');
			outword1.AddChar('K');
			outword1.AddChar('N');
			outword2.AddChar('A');
			outword2.AddChar('N');
			inword.location += 3;
		} else {
			// "[vowel]-" = "A"
			outword1.AddChar('A');
			outword2.AddChar('A');
			inword.location++;
		}
	}
}

// Converts a string
void dmetaphone::Convert (dmetaphoneword &inword, dmetaphoneword &outword1, dmetaphoneword &outword2) {
	bool done = false;
	BYTE c1, c2, c3, c4;
	while (inword.location < inword.length) {
		c1 = *(inword.word + inword.location);
		if (inword.location < inword.length - 1) {
			c2 = *(inword.word + inword.location + 1);
		} else {
			c2 = ' ';
		}
		if (inword.location < inword.length - 2) {
			c3 = *(inword.word + inword.location + 2);
		} else {
			c3 = ' ';
		}
		if (inword.location < inword.length - 3) {
			c4 = *(inword.word + inword.location + 3);
		} else {
			c4 = ' ';
		}
		if (inword.location == inword.length - 4) {
			switch (c1) {
				case 'I':
				case 'E':
					if (c2 == 'A' && c3 == 'U' && c4 == 'X') {
						outword1.AddChar('K');
						outword1.AddChar('S');
						outword2.AddChar('K');
						outword2.AddChar('S');
						inword.location += 3;
					}
					break;
				case 'O':
				case 'A':
					if (c2 == 'U' && c3 == 'X') {
						outword1.AddChar('K');
						outword1.AddChar('S');
						outword2.AddChar('K');
						outword2.AddChar('S');
						inword.location += 2;
					}
					break;
			}
		}
		// At the end of the string
		if (inword.location == inword.length - 2) {
			switch (c1) {
				// "-MB" ==> "M"
				case 'M':
					outword1.AddChar('M');
					outword2.AddChar('M');
					if (c2 == 'B') {
						inword.location++;
					} 
					break;
				case 'G':
					if (c2 == 'N') {
						outword1.AddChar('N');
						outword1.AddChar('N');
						inword.location++;
					} else {
						outword1.AddChar('K');
						outword2.AddChar('K');
					}
					break;
				default:
					break;
			}
		}
		inword.location++;
	}
}

void dmetaphone::Encode (dmetaphoneword &inword, dmetaphoneword &outword1, dmetaphoneword &outword2) {
	inword.IsSlavoGermanic = inword.SlavoGermanic();
	outword1.InitializeOut(16);
	outword2.InitializeOut(16);
	UpperCaseWord(inword.word, inword.length);
	CheckStart(inword, outword1, outword2);
	Convert(inword, outword1, outword2);
}

// Is Alphabetic Character
bool dmetaphone::IsAlpha (BYTE c) {
	return (c >= 'A' && c <= 'Z' || c == 'Ç');
}

// Is a Vowel
bool dmetaphone::IsVowel (BYTE c) {
	return (c == 'A' || c == 'E' || c == 'I' || c == 'O' || c == 'U' || c == 'Y');
}

// Is a Consonant
bool dmetaphone::IsConsonant (BYTE c) {
	return (!IsVowel (c));
}

// Add a Character to our Double Metaphone Word
void dmetaphoneword::AddChar (BYTE c)
{
	*(this->word + this->location++) = c;
}

// Initialize Double Metaphone word
void dmetaphoneword::InitializeIn (BYTE *wordin, ULONG len) {
	this->length = len;
	this->location = 0;
	this->word = new BYTE [this->length + 1];
	memcpy(this->word, wordin, len);
	*(this->word + this->length) = 0;
}

void dmetaphoneword::InitializeOut (ULONG len) {
	this->length = len;
	this->location = 0;
	this->word = new BYTE [this->length + 1];
	memset(this->word, 0, this->length + 1);
}

dmetaphoneword::~dmetaphoneword() {
	if (this->word != NULL) {
		delete [] this->word;
	}
	this->word = NULL;
}
